FFmpeg+ubuntu QTCreator:从摄像头获取MJPEG格式视频数据,先解码后再进行H264编码

您所在的位置:网站首页 ffmpeg gop和码率 FFmpeg+ubuntu QTCreator:从摄像头获取MJPEG格式视频数据,先解码后再进行H264编码

FFmpeg+ubuntu QTCreator:从摄像头获取MJPEG格式视频数据,先解码后再进行H264编码

2023-04-15 07:27| 来源: 网络整理| 查看: 265

前言

音视频技术学习记录

实现

我使用的摄像头就是笔记本电脑自带的摄像头,如果你的虚拟机还不支持调用摄像头,可以先搜索并依照相关博文的描述,让虚拟机可以正常调用摄像头。

先用如下命令查看自己的摄像头支持的采集格式:

sudo v4l2-ctl -d /dev/video0 --all

由于我的摄像头仅支持使用MJPEG格式进行视频数据的采集,而要进行H264编码,输入的数据必须是YUV420P的原始数据。所以我们需要先对MJPEG数据进行解码。

我先给出主程序操作流程:

//set log level av_log_set_level(AV_LOG_DEBUG); //上下文 AVFormatContext *fmt_ctx = NULL; AVCodecContext *codec_ctx = NULL; AVCodecContext *decodec_ctx = NULL; AVFrame *frame_decodec= NULL; AVFrame *frame_encodec= NULL; AVPacket *packet_de= NULL; AVPacket *packet_en= NULL; AVPacket pkt; int count = 0; int base = 0; //写入数据文件相关定义 char *outfileName = "/home/smallred/video_work/mediawork/11.yuv"; FILE *outFile = fopen(outfileName, "wb+"); //写 二进制 创建 char *h264fileName = "/home/smallred/video_work/mediawork/1.h264"; FILE *h264File = fopen(h264fileName, "wb+"); //写 二进制 创建 if(!outFile) { av_log(NULL, AV_LOG_INFO, "Failed to create outFile!"); goto __ERROR; } // //打开设备 获取设备格式上下文 fmt_ctx = OpenDevice(); //打开解码器 decodec_ctx = OpenDecoder(VideoWidth, VideoHeight); //打开编码器 codec_ctx = OpenEncoder(VideoWidth, VideoHeight); //创建Frame frame_decodec = CreateFrame(VideoWidth, VideoHeight, AV_PIX_FMT_YUV420P); frame_encodec = CreateFrame(VideoWidth, VideoHeight, AV_PIX_FMT_YUV420P); //创建Packet packet_de= av_packet_alloc(); packet_en= av_packet_alloc(); if(!packet_de || !packet_en) { av_log(NULL, AV_LOG_INFO, "Failed to create Packet!\n"); goto __ERROR; } //packet获取数据空间 if( av_new_packet(packet_de, 150000)< 0) { av_log(NULL, AV_LOG_INFO, "Failed to get Packet Buffer!\n"); goto __ERROR; } //读数据 while((av_read_frame(fmt_ctx, &pkt) == DeviceRead_Suc) && (audioStartorStop == true)) { av_log(NULL, AV_LOG_INFO, "Pac size = %d(%p), Count = %d \n", pkt.size, pkt.data, count ); //将数据拷贝到Packet 中 等待送入MJPEG解码器 memcpy(packet_de->data, pkt.data, pkt.size); //MJPEG解码 Decode_Mjpeg(decodec_ctx, packet_de, frame_decodec, frame_encodec, outFile); frame_encodec->pts = base++; Encode_H264(codec_ctx, frame_encodec, packet_en, h264File); count ++; av_packet_unref(&pkt); //释放pkt } //将剩余数据进行处理 Decode_Mjpeg(decodec_ctx, NULL, frame_decodec, NULL, outFile); Encode_H264(codec_ctx, NULL, packet_en, h264File); av_log(NULL, AV_LOG_INFO, "Finished"); __ERROR: //关闭文件 if(outFile) fclose(outFile); //释放 if(fmt_ctx) avformat_close_input(&fmt_ctx); if(codec_ctx) avcodec_close(codec_ctx); if(frame_decodec) av_frame_free(&frame_decodec); if(frame_encodec) av_frame_free(&frame_encodec); if(packet_de) av_packet_free(&packet_de); if(packet_en) av_packet_free(&packet_en); return;

然后是主要的MJPEG解码部分 Decode_Mjpeg(decodec_ctx, NULL, frame_decodec, NULL, outFile)接口: (因为MJPEG解码后的数据为YUV422,所以还需将YUV422数据转化为YUV420P)

/* * @brief 解码器 MJPG * @param: AVCodecContext *decodec_ctx, 解码器上下文 * AVPacket *packet, 解码器输入数据包packet * AVFrame *frame, 解码器输出数据包frame * FILE *outfile 文件名 若不用可设为NULL *@return: NULL */ void Decode_Mjpeg(AVCodecContext *decodec_ctx, AVPacket *packet, AVFrame *frame, AVFrame *frame_en, FILE *outfile) { int ret = 0; int i = 0; int j = 0; //向解码器输入数据 ret = avcodec_send_packet(decodec_ctx, packet); if(ret < 0) { av_log(NULL, AV_LOG_INFO, "Failed to send a packet to decoder!\n"); exit(1); } while(ret >= 0) {//解码器输出数据 ret = avcodec_receive_frame(decodec_ctx, frame); //若数据不足以解码或者数据用完了 if( ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){ return; }else if (ret data[1]举例 数据包宽高分别为VideoWidth/2 、VideoHeight/2 //UUUUUUUUUUUUU UUUUUUUUUUUUUU //UUUUUUUUUUUUU - > //UUUUUUUUUUUUU - >UUUUUUUUUUUUUU //UUUUUUUUUUUUU for(i = 0; i < VideoHeight/2; i ++) { for(j = 0; j < VideoWidth/2; j ++) { frame->data[1][(VideoWidth/2) * i + j] = frame->data[1][(VideoWidth/2) * i * 2+ j]; frame->data[2][(VideoWidth/2) * i + j] = frame->data[2][(VideoWidth/2) * i * 2+ j]; } } //YUV422->YUV420多余数据置0 for(i = 0; i < 307200/4; i ++) { frame->data[1][307200/4 + i] = 0; frame->data[2][307200/4 + i] = 0; } //先后向文件输出Y U V 数据 fwrite(frame->data[0], 1, 307200, outfile); fwrite(frame->data[1], 1, 307200/4, outfile); fwrite(frame->data[2], 1, 307200/4, outfile); fflush(outfile); //把数据拷贝到输入H264编码器的Frame中 memcpy(frame_en->data[0], frame->data[0], 307200); memcpy(frame_en->data[1], frame->data[1], 307200/4); memcpy(frame_en->data[2], frame->data[2], 307200/4); } av_frame_unref(frame); } }

其余接口的实现:

/* * @brief 打开设备 * @param: void *@return: AVFormatContext* fmt_ctx 设备格式上下文 */ AVFormatContext* OpenDevice(void) { //打开视频设备相关定义 AVFormatContext* fmt_ctx = NULL; //格式上下文 char *deviceName = "/dev/video0"; //视频设备 AVDictionary *options = NULL; int device_open_ret = DeviceOpen_Suc; //打开成功与否标识 char errorbuf[errorbuf_size] = {0, }; //set log level av_log_set_level(AV_LOG_DEBUG); //注册设备all avdevice_register_all(); //设置采集输入方式Format const AVInputFormat* avformat = av_find_input_format("video4linux2"); //视频设备添入 options参数 分辨率/帧率 av_dict_set(&options, "video_size", "640x480", 0); av_dict_set(&options, "framerate", "10", 0); // av_dict_set(&options, "pixel_format", "mjpeg", 0); av_dict_set(&options, "input_format", "mjpeg", 0); //avformat_open_input(输出地址,设备名, 输入方式, 解码端) if((device_open_ret = avformat_open_input(&fmt_ctx, deviceName, avformat, &options)) < DeviceOpen_Suc) //如果打开设备出错 { //返回错误码 av_strerror(device_open_ret, errorbuf, errorbuf_size); //打印错误信息 av_log(NULL, AV_LOG_INFO, "Failed to open device, [%d]%s\n", device_open_ret, errorbuf); return NULL; } return fmt_ctx; } /* * @brief 打开编码器 * @param: int width, int height 分辨率的宽高 *@return: AVCodecContext* codec_ctx 编码器上下文 */ AVCodecContext* OpenEncoder(int width, int height) { const AVCodec *codec = NULL; AVCodecContext *codec_ctx = NULL; int ret = 0; //选择libx264编码器 codec = avcodec_find_encoder_by_name("libx264"); if(!codec) { av_log(NULL, AV_LOG_INFO, "ERROR, this codec not found!\n"); exit(1); } //编码器上下文 codec_ctx = avcodec_alloc_context3(codec); if(!codec_ctx) { av_log(NULL, AV_LOG_INFO, "Failed to create Codec Ctx!\n"); exit(1); } //设置H264编码参数 //SPS / PPS codec_ctx->profile = FF_PROFILE_H264_HIGH_444; //H264 Profile codec_ctx->level = 50; //H264 Level = 5.0 //分辨率 codec_ctx->width = width; //640 codec_ctx->height = height;//480 //GOP codec_ctx->gop_size = 250; //设的大一点 codec_ctx->keyint_min = 25; //最小值 //B帧 codec_ctx->max_b_frames = 3;//b帧数量 codec_ctx->has_b_frames = 1;// 需要b帧 //参考帧 codec_ctx->refs = 3; //参考帧数量 //设置输入的YUV格式 codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; //libx264需要 YUV420 的输入 //设置码率 codec_ctx->bit_rate = 600000; // 分辨率 x 帧率 x 8位深 x 1.5(yuv420p) //设置帧率 codec_ctx->time_base = (AVRational){1, 10}; codec_ctx->framerate = (AVRational){10, 1}; ret = avcodec_open2(codec_ctx, codec, NULL); if(ret < 0) { av_log(NULL, AV_LOG_INFO, "Failed to open the codec, %s\n!", av_err2str(ret)); exit(1); } return codec_ctx; } /* * @brief 打开解码器 * @param: int width, int height 分辨率的宽高 *@return: AVCodecContext* codec_ctx 编码器上下文 */ AVCodecContext* OpenDecoder(int width, int height) { const AVCodec *decodec = NULL; AVCodecContext *decodec_ctx = NULL; int ret = 0; //选择编码器 decodec = avcodec_find_decoder(AV_CODEC_ID_MJPEG); if(!decodec) { av_log(NULL, AV_LOG_INFO, "Failed to create MJPEG Decoder!\n"); exit(1); } //编码器上下文初始化 decodec_ctx = avcodec_alloc_context3(decodec); if(!decodec_ctx) { av_log(NULL, AV_LOG_INFO, "Failed to create MJPEG Decoder Ctx!\n"); exit(1); } //分辨率 decodec_ctx->width = width; //640 decodec_ctx->height = height;//480 //这些参数设置了和没设置一样 // //设置码率 // decodec_ctx->bit_rate = 1200000; // //设置帧率 // decodec_ctx->time_base = (AVRational){1, 10}; // decodec_ctx->framerate = (AVRational){10, 1}; // //输出YUV格式 // decodec_ctx->sw_pix_fmt = AV_PIX_FMT_YUV422P; // decodec_ctx->pix_fmt = AV_PIX_FMT_YUV422P; // decodec_ctx->profile = FF_PROFILE_MJPEG_JPEG_LS; //打开解码器 ret = avcodec_open2(decodec_ctx, decodec, NULL); if(ret < 0) { av_log(NULL, AV_LOG_INFO, "Failed to open the codec, %s\n!", av_err2str(ret)); exit(1); } return decodec_ctx; } /* * @brief 创建编码器输入数据包Frame' * @param: Frame的参数:int width, int height 分辨率的宽高; 数据pix_fmt格式 enum AVPixelFormat pixfmt *@return: AVFrame* frame 编码器输入数据包 */ AVFrame* CreateFrame(int width, int height, enum AVPixelFormat pixfmt) { AVFrame *frame = NULL; int ret = 0; //frame初始化 frame = av_frame_alloc(); if(!frame) { av_log(NULL, AV_LOG_INFO, "Failed to create frame!\n"); goto __ERROR; } //设置编码器输入数据包参数 frame->width = width; frame->height = height; frame->format = pixfmt; //frame获取数据空间 ret = av_frame_get_buffer(frame, 32); if(ret < 0) { av_log(NULL, AV_LOG_INFO, "Failed to get Frame Buffer!\n"); goto __ERROR; } return frame; __ERROR: if(frame) av_frame_free(&frame); return NULL; } /* * @brief 编码器H264 * @param: AVCodecContext *decodec_ctx, 编码器上下文 * AVPacket *packet, 编码器输入数据包packet * AVFrame *frame, 编码器输出数据包frame * FILE *outfile 文件名 若不用可设为NULL *@return: NULL */ void Encode_H264(AVCodecContext *codec_ctx, AVFrame *frame, AVPacket *packet, FILE *outfile) { int ret = 0; //打印PTS if(frame) { av_log(NULL, AV_LOG_INFO, "send frame to encoder, pts=%ld\n", frame->pts); } //向编码器送数据 ret = avcodec_send_frame(codec_ctx, frame); if(ret < 0) { av_log(NULL, AV_LOG_INFO, "Failed to send Frame to encodec,ret = %d!\n", ret); exit(1); } while(ret >= 0) { //接收编码完成的数据 ret = avcodec_receive_packet(codec_ctx, packet); //如果数据不足或者数据用完了 if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) return; else if (ret < 0) { av_log(NULL, AV_LOG_INFO, "Failed to encodec!\n"); exit(1); } if(outfile) { fwrite(packet->data, 1, packet->size, outfile); av_packet_unref(packet); } } }

因为我是音视频技术初学者,实现过程还存在很多问题请大家多多包含和指正。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3